﻿/*
 *   DotTests.cs
 *   Copyright (c) 2011 - BitFox Software and Ricardo Federico Villafañes
 *   Contact: federvillaf@hotmail.com
 *   
 *   Licensing: This product is covered by Microsoft Reciprocal License (MS-RL)
 *              see https://bitfoxeval.codeplex.com/license
*/

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using BitFox.Evaluator;

namespace TestEvaluator
{
    /// <summary>
    /// Tests that probe the emulator is doing its basic work (checking handling of fox's types)
    /// </summary>
    [TestClass]
    public class DotTests
    {
        /// <summary>
        /// The evaluator on which to do tests
        /// </summary>
        Evaluator eval = new Evaluator();

        /// <summary>
        /// Empty ctor
        /// </summary>
        public DotTests()
        {
        }

        /// <summary>
        /// The context under which tests are runned.
        ///</summary>
        public TestContext TestContext { get; set; }

        /// <summary>
        /// Test than simple fields are correctly handled either by reflection or IResolvable
        /// </summary>
        [TestMethod]
        public void TestScalarField()
        {
            eval.Variables["A"] = new Target1() { ScalarInteger = 123 };
            eval.Variables["CPX"] = new Target2();

            Assert.AreEqual(135, (int)eval.Compile("a.scalarinteger + 12").Run());
            Assert.AreEqual(123, (int)eval.Compile("a.scalarinteger").Run());
            Assert.AreEqual(246, (int)eval.Compile("2 * a.scalarinteger").Run());
            Assert.AreEqual(444, (int)eval.Compile("cpx.scalar").Run());
            Assert.AreEqual(567, (int)eval.Compile("cpx.scalar+a.scalarinteger").Run());
        }

        /// <summary>
        /// Test than array fields are correctly handled either by reflection or IResolvable
        /// </summary>
        [TestMethod]
        public void TestArrayField()
        {
            eval.Variables["A"] = new Target1() { ArrayInteger = new int[,] { { 1, 3 }, { 5, 7 } } };
            eval.Variables["CPX"] = new Target2();

            eval.Context.ArrayIndexBaseZero = true;
            Assert.AreEqual(15, (int)eval.Compile("a.arrayinteger[0,1] * a.arrayinteger[1,0]").Run());
            Assert.AreEqual(22 * 7, (int)eval.Compile("a.arrayinteger[1,1] * cpx.array[3,147,11]").Run());
        }

        /// <summary>
        /// Test than properties that returns scalars are well handled
        /// </summary>
        [TestMethod]
        public void TestScalarProperty()
        {
            eval.Variables["A"] = new Target1() { ScalarProperty = 55 };
            eval.Variables["CPX"] = new Target2();

            Assert.AreEqual(67, (int)eval.Compile("a.scalarproperty + 12").Run());
            Assert.AreEqual(110, (int)eval.Compile("2 * a.scalarproperty").Run());
            Assert.AreEqual(4, (int)eval.Compile("cpx.scalarP").Run());
            Assert.AreEqual(59, (int)eval.Compile("cpx.scalarP+a.scalarproperty").Run());
        }

        /// <summary>
        /// Test than array properties are correctly handled either by reflection or IResolvable
        /// </summary>
        [TestMethod]
        public void TestArrayProperty()
        {
            eval.Variables["A"] = new Target1() { ArrayProperty = new int[,] { { 2, 4 }, { 6, 8 } } };
            eval.Variables["CPX"] = new Target2();

            eval.Context.ArrayIndexBaseZero = true;
            Assert.AreEqual(24, (int)eval.Compile("a.arrayproperty[0,1] * a.arrayproperty[1,0]").Run());
            Assert.AreEqual(33 * 8, (int)eval.Compile("a.arrayproperty[1,1] * cpx.arrayprop[3,'hello' + ' gizmo',11]").Run());
        }

        /// <summary>
        /// Test than methods are correctly invoked when they are bind to an object and the method is located through reflection.
        /// </summary>
        [TestMethod]
        public void TestReflectedMethod()
        {
            eval.Variables["A"] = new Target1();
            Assert.AreEqual(15, (int)eval.Compile("a.DoProduct(2+1, 5)").Run());
            Assert.AreEqual(48, (int)eval.Compile("a.DoProduct(4, 6) * 2").Run());
        }

        /// <summary>
        /// Test than methods are correctly invoked when they are bind to an object and IResolvable is used.
        /// </summary>
        [TestMethod]
        public void TestResolvableMethod()
        {
            eval.Variables["CPX"] = new Target2();
            Assert.AreEqual(10, (int)eval.Compile("cpx.DoSum(1,2,3,4)").Run());
            Assert.AreEqual(21, (int)eval.Compile("cpx.DoSum(1,2,3,4,5,6)").Run());
        }

        /// <summary>
        /// Extreme test than push the bind (DOT) operation to its maximum.
        /// </summary>
        [TestMethod]
        public void TestExtreme()
        {
            eval.Variables["X"] = new Target1() { ScalarProperty = 77 };
            Assert.AreEqual(77+44+(33*33)+1, (int)eval.Compile("x.ScalarProperty + x.TargetProperty.ScalarInteger + x.OtherTarget(33).ScalarInteger + 1").Run());
        }
    }

    /// <summary>
    /// Class used in those tests than access classes's member using reflection.
    /// </summary>
    public class Target1
    {
        public int ScalarInteger;
        public int[,] ArrayInteger;
        public int ScalarProperty { get; set; }
        public int[,] ArrayProperty { get; set; }
        public Target1 TargetProperty { get { return new Target1() { ScalarInteger = 44 }; } }

        public int DoProduct(int a, int b)
        {
            return a * b;
        }

        public Target1 OtherTarget(int x)
        {
            return new Target1() { ScalarInteger = x * x };
        }
    }

    /// <summary>
    /// Class used in those tests than access classes's members using IResolvable interface
    /// </summary>
    public class Target2 : IResolvable
    {
        public object SolveMethod(string name, object[] arguments)
        {
            switch (name.ToLower())
            {
                case "dosum":
                    int result = 0;
                    foreach (int i in arguments)
                        result += i;
                    return result;

                default:
                    throw new ArgumentException();
            }
        }

        public object SolveVariable(string name, object[] arrayIndexes)
        {
            switch (name.ToLower())
            {
                case "scalar":
                    if (arrayIndexes != null) 
                        throw new ArgumentNullException();
                    return 444;

                case "array":
                    if (arrayIndexes == null || arrayIndexes.Length != 3 ||
                        (int)arrayIndexes[0] != 3 || (int)arrayIndexes[1] != 147 || (int)arrayIndexes[2] != 11)
                        throw new ArgumentException();
                    return 22;

                case "scalarp":
                    if (arrayIndexes != null)
                        throw new ArgumentNullException();
                    return 4;

                case "arrayprop":
                    if (arrayIndexes == null || arrayIndexes.Length != 3 ||
                        (int)arrayIndexes[0] != 3 || (string)arrayIndexes[1] != "hello gizmo" || (int)arrayIndexes[2] != 11)
                        throw new ArgumentException();
                    return 33;

                default:
                    throw new ArgumentException();
            }
        }
    }
}
